//
/// page cache的锁是central cache给加的,为了方便释放掉自己的锁
// Created by xietianqi on 2025/1/12.

#include "include/PageCache.h"

PageCache PageCache::_sInstance;


/**
 * 递归专用的函数由于需要加锁锁已经被加在调用的地方central cache的地方统一加锁了防止递归自锁
 * 获取一个k页的span
 * @param k 页数一个页就映射一个下标 参数范围一般情况下1-128个 大于128的找系统开
 * @return 一个k页的span
 */
Span *PageCache::NewSpan(size_t kpage) {
    assert(kpage > 0);

    if (kpage > NPAGES - 1) {
        void* ptr = SystemAlloc(kpage);
        //Span* span = new Span;
        Span* span = _spanPool.New();
        span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
        span->_n = kpage;
        //_idSpanMap[span->_pageId] = span;
        _idSpanMap.set(span->_pageId, span);
        return span;
    }

    ///////////////////////////下面是小于1-128pages的时候//////////////////////////////////////
    //加锁要考虑递归自锁的问题
    // std::recursive_mutex //递归互斥锁

    // 检查第k个筒里有没有k页的span


/////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///     bug5 没缓存idSpan!!!!!!!
    //if (!_spanLists[kpage].Empty()) {
    //    return _spanLists[kpage].PopFront();
    //}
    if (!_spanLists[kpage].Empty()) {
        Span* kSpan = _spanLists[kpage].PopFront();
        for (PAGE_ID j = 0; j < kSpan->_n; j++) {
            // 直接+j单位对吗;
            //_idSpanMap[kSpan->_pageId + j] = kSpan;
            _idSpanMap.set(kSpan->_pageId + j, kSpan);
        }
        return kSpan;
    }
/////////////////////////////////////////////////////////////////////////////////////////////////////////




    // 没有正好的span检查后面的筒有没有span切分连接并返回这个切掉的值
    ///    /////// | //|/////// | //.../////
    ///            k   n'       n      n' = n - k;
    ///    /////// | //|/////// | //.../////
//_pageId  0 1 2 3 4 5 6 7 8 9 10 ...... 128
    for (size_t i = kpage + 1; i < NPAGES; ++i) {
        if (!_spanLists[i].Empty()) {
            // 先从头上摘下来然后进行切分 注意:这个是大于目标值一点的
            Span *nSpan = _spanLists[i].PopFront();
            // 切成k页的span和一个n-k页的span挂载到n-k的位置;
            //Span *kSpan = new Span;
            Span* kSpan = _spanPool.New();
            //在nspan的头部切k页下来给kspan --页号标识
            kSpan->_pageId = nSpan->_pageId;
            kSpan->_n = kpage;
            nSpan->_pageId += kpage;
            nSpan->_n -= kpage;
            // 多开的空间链入span链表
            _spanLists[nSpan->_n].PushFront(nSpan);
            /// 存储nspan的首尾页号 和nspan的映射 方便pagecache回收内存时进行合并查找
            // 头部建立映射
            _idSpanMap.set(nSpan->_pageId, nSpan);
            //尾部建立映射
            _idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);

            // 这个j是什么??页内偏移量?  建立id和span的映射方便central cache回收小块内存时查找对应span
            for (PAGE_ID j = 0; j < kSpan->_n; j++) {
                // 直接+j单位对吗;
                _idSpanMap.set(kSpan->_pageId + j, kSpan);
            }
            return kSpan;// 出口
        }
    }
    // 没有大页的span或者一开始啥也没有一直到了128位置仍然没找见-向堆申请一个128k的内存
    //Span *bigSpan = new Span;
    Span *bigSpan =  _spanPool.New();
    void *ptr = SystemAlloc(NPAGES - 1);//申请128kB的内存
    // 需要有一个指针和页号的转换方式
    bigSpan->_pageId = (PAGE_ID) ptr >> PAGE_SHIFT;
    bigSpan->_n = NPAGES - 1; // 页数
    // 连接到spanlist
    _spanLists[bigSpan->_n].PushFront(bigSpan);
    // 重走这个逻辑就行
    // 涉及到一个递归加锁的问题
    return NewSpan(kpage);
}

/**
 * 通过地址在映射表中拿到所属于的span
 * @param obj 地址-进来之后要被换算成页号
 * @return 一个span的指针
 */
Span *PageCache::MapObjectToSpan(void *obj) {
    // 通过地址换算出页号
    PAGE_ID id = (PAGE_ID) obj >> PAGE_SHIFT;
    //使用智能指针加锁 优化成基数数之后不需要加锁了
    //std::unique_lock<std::mutex> lock(_pageMtx);
    // std::unordered_map<PAGE_ID,Span*>::iterator ret = _idSpanMap.find(id);
    //auto ret = _idSpanMap.find(id);
    //if (ret != _idSpanMap.end())
    //    return ret->second;
    //else
    //    assert(false);
    auto ret = (Span*) _idSpanMap.get(id);
    assert(ret != nullptr);
    return ret;

}

/**
 * 释放空间到page cache
 * 这个要对span的前后页尝试合并
 * 根据那个128个page的列表进行缓解内存外碎片的问题!!!
 * 如何区分这个span到底在哪里? 在central cache还是page cache? 在page cache的前提是use_count = 0;
 * 以上这样是不可以的 :
 *  有可能在我还在切分的时候use_count = 0也就是我正在切呢,你就把我的这个内存拿去合并了!!!!??
 * 我们增加了一个参数叫is_use
 * @param span 含有页数页号但没freelist的span
 */
void PageCache::ReleaseSpanToPageCache(Span *span) {
    // 大于128页的直接还给堆内存
    if (span->_n > NPAGES - 1) {
        void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
        SystemFree(ptr);
        // delete span; //new 出来的span就要delete
        _spanPool.Delete(span);
        return;
    }
    // 对没有在使用着的span前后页进行合并 我有个问题,如果还回来的span都不连续,在被合并之前这个span还能被使用吗
    // 也就是说不管原先_isUse是否是true都是可以被继续使用的
    // 只不过如果要进行合并的话就要置为false

    ///while1 先找前一个连号span进行合并
    while (true) {
        // 尾页要-1
        PAGE_ID prevId = span->_pageId - 1;
        //auto pret = _idSpanMap.find(prevId);
        //// 没找见前一个连号内存
        //if (pret == _idSpanMap.end()) {
        //    break;
        //}
        // 
        auto pret = (Span*)_idSpanMap.get(prevId);
        // 没找见前一个连号内存
        if (pret == nullptr) {
            break;
        }
         
        
        // 找见了前面的连号span但是人家再用呢没法合并
        Span *prevSpan = pret;
        if (prevSpan->_isUse) {
            break;
        }
        // 当前的想要合并的span前面是有连号的且有没有人用的span但是这两块span合起来的页数超过128了
        // 126+20> 128
        if (prevSpan->_n + span->_n > NPAGES - 1) {
            break;
        }
        // 开始合并 preSpan到当前span
        span->_pageId = prevSpan->_pageId;
        span->_n += prevSpan->_n;


        ////这个时候才delete掉preSpan////
        // 在n页的span链表上把这个节点解除
        _spanLists[prevSpan->_n].Erase(prevSpan);
        //delete prevSpan;
        _spanPool.Delete(prevSpan);
    }

    ///while2 找后一个连号span进行合并
    while (true) {

        // 这里容易写成span->_pageId + span->_n + 1
        // 首页不需要做任何操作
        PAGE_ID nextId = span->_pageId + span->_n;
        //auto next = _idSpanMap.find(nextId);
        //// 没找见后一个连号内存
        //if (next == _idSpanMap.end()) {
        //    break;
        //}

        auto next = (Span*)_idSpanMap.get(nextId);
        // 没找见后一个连号内存
        if (next == nullptr) {
            break;
        }


        // 找见了后面的连号span但是人家再用呢没法合并
        Span *nextSpan = next;
        if (nextSpan->_isUse) {
            break;
        }
        // 当前的想要合并的span后面是有连号的且有没有人用的span但是这两块span合起来的页数超过128了
        // 126+20> 128
        if (nextSpan->_n + span->_n > NPAGES - 1) {
            break;
        }
        // 开始合并 nextSpan到当前span
        // 由于我在前面所以id不需要改
        span->_n += nextSpan->_n;


        ////这个时候才delete掉nextSpan////
        // 在n页的span链表上把这个节点解除
        _spanLists[nextSpan->_n].Erase(nextSpan);
        //delete nextSpan;
        _spanPool.Delete(nextSpan);
        // span都没了 可能需要在映射容器中删掉地址和映射的关系
    }
    // 前两个操作光是合并了span到一个span了并没有链到任何一个位置 // 待链接状态 应该是这个时候需要置为isUse为false
    _spanLists[span->_n].PushFront(span);
    span->_isUse = false;
    // 还需要把自己的首尾地址存到那个_idSpanMap里面方便别人来合并你
    _idSpanMap.set(span->_pageId,span);
    _idSpanMap.set(span->_pageId + span->_n - 1, span);
}
